package com.twod.feralvillage;

import com.twod.feralvillage.CollisionParameters.HitType;
import com.twod.feralvillage.GameObject.ActionType;

public class NPCComponent extends GameComponent {
    private float mPauseTime;
    private float mTargetXVelocity;
    private int mLastHitTileX;
    private int mLastHitTileY;
   
    private int mDialogEvent;
    private int mDialogIndex;
   
    private HitReactionComponent mHitReactComponent;
   
    private int[] mQueuedCommands;
    private int mQueueTop;
    private int mQueueBottom;
    private boolean mExecutingQueue;
   
    private Vector2 mPreviousPosition;
   
    private float mUpImpulse;
    private float mDownImpulse;
    private float mHorizontalImpulse;
    private float mSlowHorizontalImpulse;
    private float mAcceleration;
   
    private int mGameEvent;
    private int mGameEventIndex;
    private boolean mSpawnGameEventOnDeath;
   
    private boolean mReactToHits;
    private boolean mFlying;
    private boolean mPauseOnAttack;
   
    private float mDeathTime;
        private float mDeathFadeDelay;
   
    private static final float UP_IMPULSE = 400.0f;
    private static final float DOWN_IMPULSE = -10.0f;
    private static final float HORIZONTAL_IMPULSE = 200.0f;
    private static final float SLOW_HORIZONTAL_IMPULSE = 50.0f;
    private static final float ACCELERATION = 300.0f;
    private static final float HIT_IMPULSE = 300.0f;
    private static final float HIT_ACCELERATION = 700.0f;

    private static final float DEATH_FADE_DELAY = 4.0f;
   
    private static final float PAUSE_TIME_SHORT = 1.0f;
    private static final float PAUSE_TIME_MEDIUM = 4.0f;
    private static final float PAUSE_TIME_LONG = 8.0f;
    private static final float PAUSE_TIME_ATTACK = 1.0f;
    private static final float PAUSE_TIME_HIT_REACT = 1.0f;
   
    private static final int COMMAND_QUEUE_SIZE = 16;
   
    public NPCComponent() {
        super();
        setPhase(ComponentPhases.THINK.ordinal());
        mQueuedCommands = new int[COMMAND_QUEUE_SIZE];
        mPreviousPosition = new Vector2();
        reset();
    }
   
    @Override
    public void reset() {
        mPauseTime = 0.0f;
        mTargetXVelocity = 0.0f;
        mLastHitTileX = 0;
        mLastHitTileY = 0;
        mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
        mDialogIndex = 0;
        mHitReactComponent = null;
        mQueueTop = 0;
        mQueueBottom = 0;
        mPreviousPosition.zero();
        mExecutingQueue = false;
        mUpImpulse = UP_IMPULSE;
        mDownImpulse = DOWN_IMPULSE;
        mHorizontalImpulse = HORIZONTAL_IMPULSE;
        mSlowHorizontalImpulse = SLOW_HORIZONTAL_IMPULSE;
        mAcceleration = ACCELERATION;
        mGameEvent = -1;
        mGameEventIndex = -1;
        mSpawnGameEventOnDeath = false;
        mReactToHits = false;
        mFlying = false;
        mDeathTime = 0.0f;
        mDeathFadeDelay = DEATH_FADE_DELAY;
        mPauseOnAttack = true;
    }

    @Override
    public void update(float timeDelta, BaseObject parent) {

        GameObject parentObject = (GameObject)parent;
       
        if (mReactToHits &&
                        mPauseTime <= 0.0f &&
                        parentObject.getCurrentAction() == ActionType.HIT_REACT) {
                mPauseTime = PAUSE_TIME_HIT_REACT;
            pauseMovement(parentObject);
                parentObject.getVelocity().x = -parentObject.facingDirection.x * HIT_IMPULSE;
                parentObject.getAcceleration().x = HIT_ACCELERATION;

        } else if (parentObject.getCurrentAction() == ActionType.DEATH) {              
                if (mSpawnGameEventOnDeath && mGameEvent != -1) {
                        if (Utils.close(parentObject.getVelocity().x, 0.0f)
                                        && parentObject.touchingGround()) {

                                if (mDeathTime < mDeathFadeDelay && mDeathTime + timeDelta >= mDeathFadeDelay) {
                                        HudSystem hud = sSystemRegistry.hudSystem;
                               
                                if (hud != null) {
                                        hud.startFade(false, 1.5f);
                                        hud.sendGameEventOnFadeComplete(mGameEvent, mGameEventIndex);
                                        mGameEvent = -1;
                                }
                                }
                                mDeathTime += timeDelta;

                        }
                }
                // nothing else to do.
                return;
        } else if (parentObject.life <= 0) {
                parentObject.setCurrentAction(ActionType.DEATH);
                parentObject.getTargetVelocity().x = 0;
                return;
        } else if (parentObject.getCurrentAction() == ActionType.INVALID ||
                        (!mReactToHits && parentObject.getCurrentAction() == ActionType.HIT_REACT)) {
                parentObject.setCurrentAction(ActionType.MOVE);
        }
       
        if (mPauseTime <= 0.0f) {

            HotSpotSystem hotSpotSystem = sSystemRegistry.hotSpotSystem;

            if (hotSpotSystem != null) {
                final float centerX = parentObject.getCenteredPositionX();
                final int hitTileX = hotSpotSystem.getHitTileX(centerX);
                final int hitTileY = hotSpotSystem.getHitTileY(parentObject.getPosition().y + 10.0f);
                boolean accepted = true;

                if (hitTileX != mLastHitTileX || hitTileY != mLastHitTileY) {

                    final int hotSpot = hotSpotSystem.getHotSpotByTile(hitTileX, hitTileY);
                   
                    if (hotSpot >= HotSpotSystem.HotSpotType.NPC_GO_RIGHT && hotSpot <= HotSpotSystem.HotSpotType.NPC_SLOW) {
                        // movement-related commands are immediate
                        parentObject.setCurrentAction(ActionType.MOVE);
                        accepted = executeCommand(hotSpot, parentObject, timeDelta);
                    } else if (hotSpot == HotSpotSystem.HotSpotType.ATTACK && !mPauseOnAttack) {
                        // when mPauseOnAttack is false, attacks are also immediate.
                        accepted = executeCommand(hotSpot, parentObject, timeDelta);
                    } else if (hotSpot == HotSpotSystem.HotSpotType.NPC_RUN_QUEUED_COMMANDS) {
                        if (!mExecutingQueue && mQueueTop != mQueueBottom) {
                                mExecutingQueue = true;
                        }
                    } else if (hotSpot > HotSpotSystem.HotSpotType.NONE) {
                        queueCommand(hotSpot);
                    }
                }
               
                if (mExecutingQueue) {
                        if (mQueueTop != mQueueBottom) {
                                accepted = executeCommand(nextCommand(), parentObject, timeDelta);
                                if (accepted) {
                                        advanceQueue();
                                }
                        } else {
                                mExecutingQueue = false;
                        }
                }
               
                if (accepted) {
                        mLastHitTileX = hitTileX;
                        mLastHitTileY = hitTileY;
                }
               
            }
        } else {
            mPauseTime -= timeDelta;
            if (mPauseTime < 0.0f) {
                resumeMovement(parentObject);
                mPauseTime = 0.0f;
                parentObject.setCurrentAction(ActionType.MOVE);
            }
        }
       
        mPreviousPosition.set(parentObject.getPosition());
    }
   
    private boolean executeCommand(int hotSpot, GameObject parentObject, float timeDelta) {
        boolean hitAccepted = true;
        final CameraSystem camera = sSystemRegistry.cameraSystem;
       
        switch(hotSpot) {
        case HotSpotSystem.HotSpotType.WAIT_SHORT:
            if (mPauseTime == 0.0f) {
                mPauseTime = PAUSE_TIME_SHORT;
                pauseMovement(parentObject);
            }
            break;
        case HotSpotSystem.HotSpotType.WAIT_MEDIUM:
            if (mPauseTime == 0.0f) {
                mPauseTime = PAUSE_TIME_MEDIUM;
                pauseMovement(parentObject);
            }
            break;
        case HotSpotSystem.HotSpotType.WAIT_LONG:
            if (mPauseTime == 0.0f) {
                mPauseTime = PAUSE_TIME_LONG;
                pauseMovement(parentObject);
            }
            break;
        case HotSpotSystem.HotSpotType.ATTACK:
                if (mPauseOnAttack) {
                    if (mPauseTime == 0.0f) {
                        mPauseTime = PAUSE_TIME_ATTACK;
                        pauseMovement(parentObject);
       
                    }
                }
            parentObject.setCurrentAction(ActionType.ATTACK);

            break;
           
        case HotSpotSystem.HotSpotType.TALK:
                if (mHitReactComponent != null) {
                if (parentObject.lastReceivedHitType != HitType.COLLECT) {
                        mHitReactComponent.setSpawnGameEventOnHit(
                                        HitType.COLLECT, mDialogEvent, mDialogIndex);
                        if (parentObject.getVelocity().x != 0.0f) {
                                pauseMovement(parentObject);
                        }
                        hitAccepted = false;
                } else {
                    parentObject.setCurrentAction(ActionType.MOVE);

                        resumeMovement(parentObject);                                          
                        mHitReactComponent.setSpawnGameEventOnHit(HitType.INVALID, 0, 0);
                        parentObject.lastReceivedHitType = HitType.INVALID;
                }
                }
                break;
       
        case HotSpotSystem.HotSpotType.WALK_AND_TALK:
                if (mDialogEvent != GameFlowEvent.EVENT_INVALID) {
                        LevelSystem level = sSystemRegistry.levelSystem;
                        level.sendGameEvent(mDialogEvent, mDialogIndex, true);
                        mDialogEvent = GameFlowEvent.EVENT_INVALID;
                }
                break;
               
        case HotSpotSystem.HotSpotType.TAKE_CAMERA_FOCUS:
                if (camera != null) {
                        camera.setTarget(parentObject);
                }
                break;
               
        case HotSpotSystem.HotSpotType.RELEASE_CAMERA_FOCUS:

                if (camera != null) {
                        GameObjectManager gameObjectManager = sSystemRegistry.gameObjectManager;
                        camera.setTarget(gameObjectManager.getPlayer());
                }
                break;
               
        case HotSpotSystem.HotSpotType.END_LEVEL:
                HudSystem hud = sSystemRegistry.hudSystem;
               
                if (hud != null) {
                        hud.startFade(false, 1.5f);
                        hud.sendGameEventOnFadeComplete(GameFlowEvent.EVENT_GO_TO_NEXT_LEVEL, 0);
                }
                break;
        case HotSpotSystem.HotSpotType.GAME_EVENT:
                if (mGameEvent != -1) {
                        LevelSystem level = sSystemRegistry.levelSystem;
                        if (level != null) {
                                level.sendGameEvent(mGameEvent, mGameEventIndex, true);
                                mGameEvent = -1;
                        }
                }
                break;
               
        case HotSpotSystem.HotSpotType.NPC_GO_UP_FROM_GROUND:
            if (!parentObject.touchingGround()) {
                hitAccepted = false;
                break;
            }
            // fall through
        case HotSpotSystem.HotSpotType.NPC_GO_UP:
                parentObject.getVelocity().y = mUpImpulse;
                parentObject.getTargetVelocity().y = 0.0f;
            mTargetXVelocity = 0.0f;
           
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_FROM_CEILING:
            if (!parentObject.touchingCeiling()) {
                hitAccepted = false;
                break;
            }
            // fall through
        case HotSpotSystem.HotSpotType.NPC_GO_DOWN:
                parentObject.getVelocity().y = mDownImpulse;
                parentObject.getTargetVelocity().y = 0.0f;
                if (mFlying) {
                        mTargetXVelocity = 0.0f;
                }
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_LEFT:
                parentObject.getTargetVelocity().x = -mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;
                if (mFlying) {
                        parentObject.getVelocity().y = 0.0f;
                        parentObject.getTargetVelocity().y = 0.0f;
                }
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_RIGHT:
                parentObject.getTargetVelocity().x = mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;
                if (mFlying) {
                        parentObject.getVelocity().y = 0.0f;
                        parentObject.getTargetVelocity().y = 0.0f;
                }

            break;
        case HotSpotSystem.HotSpotType.NPC_GO_UP_RIGHT:
                parentObject.getVelocity().y = mUpImpulse;
                parentObject.getTargetVelocity().x = mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;

           
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_UP_LEFT:
                parentObject.getVelocity().y = mUpImpulse;
                parentObject.getTargetVelocity().x = -mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;

           
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_RIGHT:
                parentObject.getVelocity().y = mDownImpulse;
                parentObject.getTargetVelocity().x = mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;

           
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_DOWN_LEFT:
                parentObject.getVelocity().y = mDownImpulse;
                parentObject.getTargetVelocity().x = -mHorizontalImpulse;
                parentObject.getAcceleration().x = mAcceleration;

           
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_TOWARDS_PLAYER:
            int direction = 1;
            GameObjectManager manager = sSystemRegistry.gameObjectManager;
            if (manager != null) {
                GameObject player = manager.getPlayer();
                if (player != null) {
                    direction = Utils.sign(
                            player.getCenteredPositionX() -
                            parentObject.getCenteredPositionX());
                }
            }
            parentObject.getTargetVelocity().x = mHorizontalImpulse * direction;
            if (mFlying) {
                parentObject.getVelocity().y = 0.0f;
                        parentObject.getTargetVelocity().y = 0.0f;
                }
            break;
        case HotSpotSystem.HotSpotType.NPC_GO_RANDOM:
                parentObject.getTargetVelocity().x = mHorizontalImpulse * (Math.random() > 0.5f ? -1.0f : 1.0f);
                if (mFlying) {
                        parentObject.getVelocity().y = 0.0f;
                        parentObject.getTargetVelocity().y = 0.0f;
                }
            break;
       
        case HotSpotSystem.HotSpotType.NPC_STOP:
                parentObject.getTargetVelocity().x = 0.0f;
                parentObject.getVelocity().x = 0.0f;
            break;
       
        case HotSpotSystem.HotSpotType.NPC_SLOW:
                parentObject.getTargetVelocity().x = mSlowHorizontalImpulse * Utils.sign(parentObject.getTargetVelocity().x);
            break;
           
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_2:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_3:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_4:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_5:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_2:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_3:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_4:
        case HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_5:
                selectDialog(hotSpot);
                break;
        case HotSpotSystem.HotSpotType.NONE:
            if (parentObject.touchingGround() && parentObject.getVelocity().y <= 0.0f) {
                //resumeMovement(parentObject);
            }
            break;
        }
       
        return hitAccepted;
    }
   
    private void pauseMovement(GameObject parentObject) {
        mTargetXVelocity = parentObject.getTargetVelocity().x;
        parentObject.getTargetVelocity().x = 0.0f;
        parentObject.getVelocity().x = 0.0f;
    }
   
    private void resumeMovement(GameObject parentObject) {
        parentObject.getTargetVelocity().x = mTargetXVelocity;
        parentObject.getAcceleration().x = mAcceleration;
    }
   
    private void selectDialog(int hitSpot) {
                mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER1;
        mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_1_1;
       
        if (hitSpot >= HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1) {
                mDialogEvent = GameFlowEvent.EVENT_SHOW_DIALOG_CHARACTER2;
                mDialogIndex = hitSpot - HotSpotSystem.HotSpotType.NPC_SELECT_DIALOG_2_1;
        }
    }
   
    private int nextCommand() {
        int result = HotSpotSystem.HotSpotType.NONE;
        if (mQueueTop != mQueueBottom) {
                result = mQueuedCommands[mQueueTop];
        }
        return result;
    }
   
    private int advanceQueue() {
        int result = HotSpotSystem.HotSpotType.NONE;
        if (mQueueTop != mQueueBottom) {
                result = mQueuedCommands[mQueueTop];
                mQueueTop = (mQueueTop + 1) % COMMAND_QUEUE_SIZE;
        }
        return result;
    }
   
    private void queueCommand(int hotspot) {
        int nextSlot = (mQueueBottom + 1) % COMMAND_QUEUE_SIZE;
        if (nextSlot != mQueueTop) { // only comply if there is space left in the buffer
                mQueuedCommands[mQueueBottom] = hotspot;
                mQueueBottom = nextSlot;
        }
    }
   
    public void setHitReactionComponent(HitReactionComponent hitReact) {
        mHitReactComponent = hitReact;
    }
   
    public void setSpeeds(float horizontalImpulse, float slowHorizontalImpulse, float upImpulse, float downImpulse, float acceleration) {
        mHorizontalImpulse = horizontalImpulse;
        mSlowHorizontalImpulse = slowHorizontalImpulse;
        mUpImpulse = upImpulse;
        mDownImpulse = downImpulse;
        mAcceleration = acceleration;
    }
   
    public void setGameEvent(int event, int index, boolean spawnOnDeath) {
        mGameEvent = event;
        mGameEventIndex = index;
        mSpawnGameEventOnDeath = spawnOnDeath;
    }
   
    public void setReactToHits(boolean react) {
        mReactToHits = react;
    }

    public void setFlying(boolean flying) {
        mFlying = flying;
    }
   
    public void setPauseOnAttack(boolean pauseOnAttack) {
        mPauseOnAttack = pauseOnAttack;
    }
}

